/*
 * Created by Christopher King
 * Copyright (C) 2007 Christopher King, All Rights Reserved.
 */

package us.cirion.adventure.ui.mobile;

import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.midlet.MIDlet;

import us.cirion.adventure.engine.Direction;
import us.cirion.adventure.engine.Word;

/**
 * HUDCanvas is the main user interface component of the Treasure Chest text
 * game engine. It accepts all player input and draws all text and images to the
 * screen.
 * 
 * @author Chris
 */
class HUDCanvas extends Canvas implements CommandListener
{

	private static final int DEFAULT_TEXT_XPOS = 0;
	private static final int DEFAULT_TEXT_YPOS = 0;
	private static final int DEFAULT_TEXT_WIDTH = 100;
	private static final int DEFAULT_TEXT_HEIGHT = 100;

	private InputListener inputListener;
	private MIDlet midlet;

	private Vector messages;
	private String roomText;
	private int visibleTextIndex;
	private int linesOfTextPerScreen;
	private Vector words;
	private Vector directions;
	private Font buttonFont;
	private Font messageFont;

	private Vector buttons;
	private Vector cardinals;

	private int xTextWindowPos;
	private int yTextWindowPos;
	private int widthTextWindow;
	private int heightTextWindow;

	private Image backgroundImage;

	private int visibleWordIndex;

	private boolean isExitRequested = false;
	private boolean isAcceptingInput = true;

	private Command moreTextCommand, previousTextCommand;

	public HUDCanvas(InputListener inputListener, MIDlet midlet)
	{
		buttonFont = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN,
				Font.SIZE_SMALL);
		messageFont = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN,
				Font.SIZE_MEDIUM);
		messages = new Vector();
		words = new Vector();
		buttons = new Vector();
		cardinals = new Vector();
		this.inputListener = inputListener;
		this.midlet = midlet;

		moreTextCommand = new Command("More", Command.SCREEN, 0);
		previousTextCommand = new Command("Prev", "Previous", Command.SCREEN, 1);

		setCommandListener(this);

		this.xTextWindowPos = DEFAULT_TEXT_XPOS;
		this.yTextWindowPos = DEFAULT_TEXT_YPOS;
		this.widthTextWindow = DEFAULT_TEXT_WIDTH;
		this.heightTextWindow = DEFAULT_TEXT_HEIGHT;
	}

	public void setInputListener(InputListener listener)
	{
		this.inputListener = listener;
	}

	public InputListener getInputListener()
	{
		return inputListener;
	}

	public void configureTextWindow(int xTextWindowPos, int yTextWindowPos,
			int widthTextWindow, int heightTextWindow)
	{
		this.xTextWindowPos = xTextWindowPos;
		this.yTextWindowPos = yTextWindowPos;
		this.widthTextWindow = widthTextWindow;
		this.heightTextWindow = heightTextWindow;
		this.linesOfTextPerScreen = this.heightTextWindow
				/ messageFont.getHeight();
	}

	public void setBackgroundImage(Image backgroundImage)
	{
		this.backgroundImage = backgroundImage;
	}

	public void addButtonLayout(int x, int y, int width, int height)
	{
		buttons.addElement(new ButtonLayout(x, y, width, height));
	}

	public void addCardinalLayout(int cardinal, int x, int y)
	{
		if (cardinal >= cardinals.size())
		{
			cardinals.setSize((cardinal + 1) * 2);
		}
		cardinals.setElementAt(new CardinalLayout(cardinal, x, y), cardinal);
	}

	public void setExitRequested(boolean isExitRequested)
	{
		this.isExitRequested = isExitRequested;
	}

	public boolean isAcceptingInput()
	{
		return isAcceptingInput;
	}

	public void setAcceptingInput(boolean isAcceptingInput)
	{
		this.isAcceptingInput = isAcceptingInput;
	}

	private String skipOpeningWhitespace(String string)
	{
		int charsToSkip = 0;
		for (int i = 0; i < string.length(); ++i)
		{
			if (string.charAt(i) == ' ')
			{
				++charsToSkip;
			} else if (string.charAt(i) == '\t')
			{
				++charsToSkip;
			} else
			{
				break;
			}
		}
		return string.substring(charsToSkip);
	}

	// Posted on j2meforums.com
	public boolean drawText(Graphics graphics, String testS, int x, int y,
			int startAtLine)
	{
		// There's opportunity for optimization here - could create all lines
		// when we first get a
		// new message. Check to see if there's a bottleneck.
		MultiLineText wp = new MultiLineText(messageFont, testS, widthTextWindow);
		int start = 0, next = -1;
		int linesCreated = 0;
		if (wp != null)
		{
			while ((next = wp.next()) != -1
					&& linesCreated < startAtLine + linesOfTextPerScreen)
			{
				if (linesCreated >= startAtLine)
				{
					graphics.drawString(skipOpeningWhitespace(testS.substring(
							start, next)), x, y, Graphics.TOP | Graphics.LEFT);
					y += messageFont.getHeight();
				}
				start = next;
				++linesCreated;
			}
		}
		return next == -1;
	}

	private void displayText(Graphics graphics, String text)
	{
		graphics.setFont(messageFont);
		if (drawText(graphics, text, xTextWindowPos, yTextWindowPos,
				visibleTextIndex))
		{
			// All the text is displayed, nothing more to show.
			removeCommand(moreTextCommand);
		} else
		{
			// We're missing some text
			addCommand(moreTextCommand);
		}
	}

	private void drawTextOnButton(Graphics graphics, ButtonLayout button,
			String text)
	{
		graphics.setClip(button.getX(), button.getY(), button.getWidth(),
				button.getHeight());
		graphics.drawString(text, button.getX() + button.getWidth() / 2, button
				.getY(), Graphics.HCENTER | Graphics.TOP);
		graphics.setClip(0, 0, backgroundImage.getWidth(), backgroundImage
				.getHeight());
	}

	private void displayCardinals(Graphics graphics)
	{
		graphics.setFont(buttonFont);
		if (directions != null)
		{
			for (int i = 0; i < directions.size(); ++i)
			{
				Direction direction = (Direction) directions.elementAt(i);
				if (direction != null)
				{
					CardinalLayout layout = (CardinalLayout) cardinals
							.elementAt(direction.getCardinal());
					if (layout != null)
					{
						graphics.drawString(direction.getDisplayText(), layout
								.getX(), layout.getY(), Graphics.HCENTER
								| Graphics.BASELINE);
					}
				}
			}
		}
	}

	private void displayButtons(Graphics graphics)
	{
		graphics.setFont(buttonFont);
		int availableButtons = buttons.size();
		if (words.size() > availableButtons)
		{
			drawTextOnButton(graphics, (ButtonLayout) buttons.lastElement(),
					"More...");
			--availableButtons;
		}
		for (int i = 0; i < availableButtons
				&& i + visibleWordIndex < words.size(); ++i)
		{
			drawTextOnButton(graphics, (ButtonLayout) buttons.elementAt(i),
					((Word) words.elementAt(i + visibleWordIndex))
							.getDisplayText());
		}
	}

	protected void paint(Graphics graphics)
	{
		graphics.fillRect(0, 0, getWidth(), getHeight());

		graphics.drawImage(backgroundImage, 0, 0, Graphics.TOP | Graphics.LEFT);

		// If we have any messages, display them.
		if (messages.size() > 0)
		{
			displayText(graphics, (String) messages.elementAt(0));
			graphics.setFont(buttonFont);
			if (isAcceptingInput)
			{
				drawTextOnButton(graphics, (ButtonLayout) buttons
						.firstElement(), "Done");
			}
		} else if (isExitRequested)
		{
			midlet.notifyDestroyed();
		} else if (roomText != null)
		{
			// Otherwise, we're playing the game.
			displayText(graphics, roomText);
			displayButtons(graphics);
			displayCardinals(graphics);
		}
	}

	public void resetWords()
	{
		visibleWordIndex = 0;
		this.words.removeAllElements();
	}

	public void addWords(Vector words)
	{
		Enumeration insert = words.elements();
		while (insert.hasMoreElements())
		{
			this.words.addElement(insert.nextElement());
		}
	}

	public void setRoomText(String text)
	{
		resetTextNavigation();
		roomText = text;
	}

	private void resetTextNavigation()
	{
		removeCommand(previousTextCommand);
		removeCommand(moreTextCommand);

		visibleTextIndex = 0;
		visibleWordIndex = 0;
	}

	public void displayMessage(String message)
	{
		resetTextNavigation();
		messages.addElement(message);
	}

	public void setDirections(Vector directions)
	{
		// If it's a cardinal direction, we'll stick it in that vector.
		// Otherwise, we'll need to add it to the set of Words so it will
		// be available to the user.
		this.directions = new Vector(cardinals.size() + 1);
		this.directions.setSize(cardinals.size() + 1);
		for (int i = 0; i < directions.size(); ++i)
		{
			Direction direction = (Direction) directions.elementAt(i);
			if (cardinals.elementAt(direction.getCardinal()) == null)
			{
				this.words.addElement(direction);
			} else
			{
				this.directions
						.setElementAt(direction, direction.getCardinal());
			}
		}
	}

	public void commandAction(Command c, Displayable s)
	{
		if (c.getCommandType() == Command.EXIT)
		{
			midlet.notifyDestroyed();
			return;
		}

		if (c == moreTextCommand)
		{
			visibleTextIndex += linesOfTextPerScreen;

			addCommand(previousTextCommand);

			repaint();
		} else if (c == previousTextCommand)
		{
			visibleTextIndex -= linesOfTextPerScreen;
			if (visibleTextIndex == 0)
			{
				removeCommand(previousTextCommand);
			}
			repaint();
		}
	}

	// Update the engine with the user's choice. Also reset our internal state
	// to protect against the user continuing to enter commands before the
	// engine updates the state.
	private void processWord(Word word)
	{
		if (word != null)
		{
			inputListener.handleWord(word);
		}
	}

	// This makes some assumptions about how buttons are defined and laid out.
	// To be extra-safe, should probably define button values in configuration.
	protected void keyPressed(int keyCode)
	{
		int keyIndex = -1;
		if (keyCode >= KEY_NUM1 && keyCode <= KEY_NUM9)
		{
			keyIndex = keyCode - KEY_NUM1;
		} else if (keyCode == KEY_STAR)
		{
			keyIndex = 9;
		} else if (keyCode == KEY_NUM0)
		{
			keyIndex = 10;
		} else if (keyCode == KEY_POUND)
		{
			keyIndex = 11;
		}

		if (keyIndex == 11 && words.size() > buttons.size())
		{
			// User has pressed "More".
			if (visibleWordIndex + 11 < words.size())
			{
				visibleWordIndex += 11;
			} else
			{
				// Loop around to the start.
				visibleWordIndex = 0;
			}
			repaint();
		} else if (keyIndex > -1)
		{
			// For now, any key press dismisses a message.
			if (messages.size() > 0)
			{
				if (isAcceptingInput)
				{
					messages.removeElement(messages.firstElement());
					resetTextNavigation();
					repaint();
				}
			} else if (visibleWordIndex + keyIndex < words.size())
			{
				processWord((Word) words.elementAt(visibleWordIndex + keyIndex));
			}
		} else if (messages.size() == 0) // Don't allow movement while we're
											// showing a message.
		{
			int action = getGameAction(keyCode);
			if (action == UP)
			{
				processWord((Direction) directions.elementAt(Direction.UP));
			} else if (action == DOWN)
			{
				processWord((Direction) directions.elementAt(Direction.DOWN));
			} else if (action == LEFT)
			{
				processWord((Direction) directions.elementAt(Direction.LEFT));
			} else if (action == RIGHT)
			{
				processWord((Direction) directions.elementAt(Direction.RIGHT));
			}
		}
	}

}
